#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdlib.h>

#include "../inc/jpg.h"     // 包含LCD和JPG相关的宏定义（如FB_SIZE屏幕大小）
#include "../inc/jpeglib.h" // 包含JPEG解码库的头文件
#include "../inc/bmp.h"

#include <sys/stat.h>

// 全局缓冲区：用于存储JPEG解码后的颜色数据（RGB值）
static char g_color_buf[FB_SIZE] = {0};

// 全局变量：LCD设备文件描述符和内存映射地址
static int g_fb_fd;       // LCD设备文件句柄
static int *g_pfb_memory; // 映射到用户空间的LCD显存地址

/* 来自video_chat.c的全局变量：用于存储JPG图片的坐标信息 */
volatile int g_jpg_in_jpg_x; // JPG图片显示的X坐标
volatile int g_jpg_in_jpg_y; // JPG图片显示的Y坐标

/**
 * 获取文件大小
 * @param pfile_path：文件路径
 * @return 成功返回文件大小（字节），失败返回-1
 */
unsigned long file_size_get(const char *pfile_path)
{
    unsigned long filesize = -1; // 初始化为-1（表示失败）
    struct stat statbuff;        // 用于存储文件状态的结构体

    // 获取文件状态（包括大小）
    if (stat(pfile_path, &statbuff) < 0)
    {
        return filesize; // 获取失败，返回-1
    }
    else
    {
        filesize = statbuff.st_size; // 从状态中提取文件大小
    }
    return filesize;
}

/**
 * 在LCD上绘制单个点
 * @param x：点的X坐标
 * @param y：点的Y坐标
 * @param color：点的颜色（RGB格式，32位整数）
 */
void lcd_draw_point(unsigned int x, unsigned int y, unsigned int color)
{
    // 直接操作显存：将颜色值写入(x,y)对应的内存地址
    // 显存按行存储，每行800个像素（由开发板屏幕分辨率决定）
    *(g_pfb_memory + y * 800 + x) = color;
}

/**
 * 在LCD上绘制JPG图片
 * @param x：图片左上角的X坐标
 * @param y：图片左上角的Y坐标
 * @param pjpg_path：JPG文件路径（为NULL时使用内存中的缓冲区）
 * @param pjpg_buf：存储JPG数据的内存缓冲区（pjpg_path为NULL时有效）
 * @param jpg_buf_size：内存缓冲区中JPG数据的大小
 * @param jpg_half：是否将图片缩小为原来的1/2（1=缩小，0=正常大小）
 * @return 成功返回0，失败返回-1
 */
int lcd_draw_jpg(unsigned int x, unsigned int y, const char *pjpg_path,
                 char *pjpg_buf, unsigned int jpg_buf_size, unsigned int jpg_half)
{
    // 打开LCD设备（帧缓冲设备/dev/fb0）
    g_fb_fd = open("/dev/fb0", O_RDWR); // 以读写方式打开
    if (g_fb_fd < 0)
    {
        printf("打开LCD设备失败\n");
        return -1;
    }

    // 将LCD显存映射到用户空间（方便直接操作显存）
    g_pfb_memory = (int *)mmap(
        NULL,                   // 系统自动分配映射地址
        FB_SIZE,                // 映射大小（屏幕总字节数，由FB_SIZE定义）
        PROT_READ | PROT_WRITE, // 映射区域可读写
        MAP_SHARED,             // 共享映射（修改会反映到实际设备）
        g_fb_fd,                // 要映射的LCD设备句柄
        0                       // 映射偏移量（从设备起始地址开始）
    );
    if (g_pfb_memory == MAP_FAILED)
    {
        printf("映射LCD设备失败\n");
        close(g_fb_fd);
        return -1;
    }

    /* JPEG解码相关结构体 */
    struct jpeg_decompress_struct cinfo; // JPEG解压主结构体
    struct jpeg_error_mgr jerr;          // JPEG错误处理结构体

    char *pcolor_buf = g_color_buf; // 指向颜色缓冲区的指针
    char *pjpg;                     // 指向JPG数据的指针

    unsigned int i = 0;     // 循环计数器
    unsigned int color = 0; // 存储转换后的RGB颜色值
    unsigned int count = 0; // 计数用（未使用）

    unsigned int x_s = x; // 图片显示的起始X坐标
    unsigned int x_e;     // 图片显示的结束X坐标
    unsigned int y_e;     // 图片显示的结束Y坐标

    int jpg_fd;            // JPG文件的句柄
    unsigned int jpg_size; // JPG文件的大小

    unsigned int jpg_width;  // JPG图片的宽度（像素）
    unsigned int jpg_height; // JPG图片的高度（像素）

    // 从文件读取JPG数据（如果指定了文件路径）
    if (pjpg_path != NULL)
    {
        // 打开JPG文件
        jpg_fd = open(pjpg_path, O_RDWR);
        if (jpg_fd == -1)
        {
            printf("打开JPG文件 %s 失败\n", pjpg_path);
            return -1;
        }

        // 获取JPG文件大小
        jpg_size = file_size_get(pjpg_path);

        // 分配内存用于存储JPG文件数据
        pjpg = malloc(jpg_size);

        // 读取JPG文件内容到内存缓冲区
        read(jpg_fd, pjpg, jpg_size);
    }
    // 从内存缓冲区读取JPG数据（未指定文件路径时）
    else
    {
        jpg_size = jpg_buf_size; // 使用传入的缓冲区大小
        pjpg = pjpg_buf;         // 指向传入的缓冲区
    }

    /* 初始化JPEG解码结构体 */
    cinfo.err = jpeg_std_error(&jerr); // 绑定错误处理结构体

    /* 创建JPEG解码器实例 */
    jpeg_create_decompress(&cinfo);

    /* 设置JPEG数据源（从内存中解码） */
    jpeg_mem_src(&cinfo, pjpg, jpg_size);

    /* 读取JPEG文件头，获取图片信息（宽、高等） */
    jpeg_read_header(&cinfo, TRUE);

    /* 开始解码 */
    jpeg_start_decompress(&cinfo);

    // 调试代码
    printf("图片宽度：%d\n", cinfo.output_width);
    printf("图片高度：%d\n", cinfo.output_height);
    // printf("FB_SIZE: %d，图片每行所需字节：%d\n", FB_SIZE, cinfo.output_width * 3);
    // if (cinfo.output_width * 3 > FB_SIZE)
    // {
    //     printf("错误：缓冲区太小，无法容纳一行像素数据！\n");
    // }

    // 如果需要将图片缩小为1/2
    if (jpg_half)
    {
        // 计算缩小后的图片结束坐标
        x_e = x_s + (cinfo.output_width / 2); // 宽度减半
        y_e = y + (cinfo.output_height / 2);  // 高度减半

        /* 循环读取解码后的扫描线（一行像素），直到所有行处理完毕 */
        while (cinfo.output_scanline < cinfo.output_height)
        {
            pcolor_buf = g_color_buf; // 重置颜色缓冲区指针

            // 读取一行像素数据到颜色缓冲区（JPEG解码后为RGB格式）
            jpeg_read_scanlines(&cinfo, (JSAMPARRAY)&pcolor_buf, 1);

            // 跳过一行（实现高度减半：每读2行只显示1行）
            jpeg_read_scanlines(&cinfo, (JSAMPARRAY)&pcolor_buf, 1);

            // 处理一行中的像素（宽度减半：每2个像素只显示1个）
            for (i = 0; i < (cinfo.output_width / 2); i++)
            {
                // 从RGB数据转换为LCD需要的颜色格式（R<<16 | G<<8 | B）
                color = *(pcolor_buf + 2);       // B分量
                color |= *(pcolor_buf + 1) << 8; // G分量
                color |= *(pcolor_buf) << 16;    // R分量

                // 在LCD上绘制该像素
                lcd_draw_point(x, y, color);

                pcolor_buf += 6; // 跳过下一个像素（宽度减半）
                x++;             // X坐标递增
            }

            y++;     // Y坐标递增（处理下一行）
            x = x_s; // 重置X坐标到起始位置
        }
    }
    // 正常大小显示图片（不缩小）
    else
    {
        // 计算正常大小的图片结束坐标
        x_e = x_s + cinfo.output_width; // 宽度为原图宽度
        y_e = y + cinfo.output_height;  // 高度为原图高度

        /* 循环读取解码后的扫描线，直到所有行处理完毕 */
        while (cinfo.output_scanline < cinfo.output_height)
        {
            pcolor_buf = g_color_buf; // 重置颜色缓冲区指针

            // 读取一行像素数据到颜色缓冲区
            jpeg_read_scanlines(&cinfo, (JSAMPARRAY)&pcolor_buf, 1);

            // 处理一行中的每个像素
            for (i = 0; i < cinfo.output_width; i++)
            {
                // 转换RGB数据为LCD颜色格式
                color = *(pcolor_buf + 2);       // B分量
                color |= *(pcolor_buf + 1) << 8; // G分量
                color |= *(pcolor_buf) << 16;    // R分量

                // 在LCD上绘制该像素
                lcd_draw_point(x, y, color);

                pcolor_buf += 3; // 移动到下一个像素的RGB数据（每个像素占3字节）
                x++;             // X坐标递增
            }

            y++;     // Y坐标递增（处理下一行）
            x = x_s; // 重置X坐标到起始位置
        }
    }

    /* 结束解码并释放资源 */
    jpeg_finish_decompress(&cinfo);  // 完成解码
    jpeg_destroy_decompress(&cinfo); // 销毁解码器实例

    // 如果是从文件读取的JPG数据，释放相关资源
    if (pjpg_path != NULL)
    {
        close(jpg_fd); // 关闭JPG文件
        free(pjpg);    // 释放存储JPG数据的内存
    }

    // 释放LCD相关资源
    munmap(g_pfb_memory, FB_SIZE); // 解除显存映射
    close(g_fb_fd);                // 关闭LCD设备

    return 0;
}

// 自动播放广告图片(lcd-jpg)
void auto_display(char *adverimg[])
{
    // 轮播广告图片
    while (1)
    {
        // 显示第一张，停留3秒
        lcd_draw_jpg(0, 0, adverimg[0], NULL, 0, 0);
        sleep(3);

        // 显示第二张，停留3秒
        lcd_draw_jpg(0, 0, adverimg[1], NULL, 0, 0);
        sleep(3);

        // 显示第三张，停留3秒
        lcd_draw_jpg(0, 0, adverimg[2], NULL, 0, 0);
        sleep(3);

        // 显示第四张，停留3秒
        lcd_draw_jpg(0, 0, adverimg[3], NULL, 0, 0);
        sleep(3);
    }
}
// 显示楼层
void floor_display(char *floorimg[], char *arrowimg[])
{
    lcd_draw_jpg(400, 280, floorimg[0], NULL, 0, 0); // 开始的时候没有目标楼层，就显示在1楼
    while (1)//重复检查是否有新的目标楼层，从而进行实时的楼层显示
    {
        // 只有当目标楼层不等于当前楼层时才显示
        if (target_floor != current_floor && target_floor > 0 && target_floor <= MAX_FLOOR)
        {
            int current = current_floor;
            int target = target_floor;

            // 目标楼层 > 当前楼层：递增显示
            if (target > current)
            {
                for (int i = current; i <= target; i++)
                {
                    // 检查数组索引是否越界
                    if (i < 1 || i > MAX_FLOOR)
                    {
                        printf("警告：楼层索引越界 %d\n", i);
                        break;
                    }
                    lcd_draw_jpg(400, 280, floorimg[i - 1], NULL, 0, 0); // 注意数组索引从0开始
                    lcd_draw_jpg(400, 80, arrowimg[0], NULL, 0, 0);      // 显示箭头(up)

                    // 到达目标楼层时蜂鸣
                    if (i == target)
                    {
                        // beep();
                        current_floor = target; // 更新当前楼层
                        printf("到达楼层：%d\n", i);
                        break;
                    }
                    else
                    {
                        sleep(3); // 未到目标时延迟
                    }
                }
            }
            // 目标楼层 < 当前楼层：递减显示
            else if (target < current)
            {
                for (int i = current; i >= target; i--)
                {
                    if (i < 1 || i > MAX_FLOOR)
                    {
                        printf("警告：楼层索引越界 %d\n", i);
                        break;
                    }
                    lcd_draw_jpg(400, 280, floorimg[i - 1], NULL, 0, 0); // 注意数组索引从0开始
                    lcd_draw_jpg(400, 80, arrowimg[1], NULL, 0, 0);      // 显示箭头(down)

                    if (i == target)
                    {
                        // beep();
                        current_floor = target; // 更新当前楼层
                        printf("到达楼层：%d\n", i);
                        break;
                    }
                    else
                    {
                        sleep(3);
                    }
                }
            }
        }
        sleep(1); // 避免过度占用CPU
    }
}